#include "net.h"
#include "icmp.h"
#include "ip.h"

/**
 * @brief 发送icmp响应
 * 
 * @param req_buf 收到的icmp请求包
 * @param src_ip 源ip地址
 */
static void icmp_resp(buf_t *req_buf, uint8_t *src_ip)
{
    // TO-DO
    // 封装报头和数据
    // buf_init(&txbuf, req_buf->len);
    buf_copy(&txbuf, req_buf, 0);
    icmp_hdr_t* hdr = (icmp_hdr_t*)txbuf.data;
    // icmp_hdr_t* req_hdr = (icmp_hdr_t*)req_buf->data;
    hdr->type = ICMP_TYPE_ECHO_REPLY;
    hdr->code = 0;
    // hdr->id16 = req_hdr->id16;
    // hdr->seq16 = req_hdr->seq16;

    // 填写校验和
    hdr->checksum16 = 0;
    hdr->checksum16 = checksum16((uint16_t*)hdr, req_buf->len);

    // 发送数据报
    ip_out(&txbuf, src_ip, NET_PROTOCOL_ICMP);
}

/**
 * @brief 处理一个收到的数据包
 * 
 * @param buf 要处理的数据包
 * @param src_ip 源ip地址
 */
void icmp_in(buf_t *buf, uint8_t *src_ip)
{
    // TO-DO
    // 如果数据包的长度小于ICMP头部长度, 丢弃不处理
    if(buf->len < sizeof(icmp_hdr_t)) return;

    // 查看该报文的ICMP类型是否为回显请求, 如果是，则回送一个回显应答(ping 应答)
    icmp_hdr_t* hdr = (icmp_hdr_t*)buf->data;
    if(hdr->type == ICMP_TYPE_ECHO_REQUEST)
        icmp_resp(buf, src_ip);
}

/**
 * @brief 发送icmp不可达
 * 
 * @param recv_buf 收到的ip数据包
 * @param src_ip 源ip地址
 * @param code icmp code，协议不可达或端口不可达
 */
void icmp_unreachable(buf_t *recv_buf, uint8_t *src_ip, icmp_code_t code)
{
    // TO-DO
    // 填写ICMP报头首部
    buf_init(&txbuf, sizeof(icmp_hdr_t) + sizeof(ip_hdr_t) + 8);
    icmp_hdr_t* hdr = (icmp_hdr_t*)txbuf.data;
    hdr->type = ICMP_TYPE_UNREACH;
    hdr->code = code;
    hdr->id16 = 0;
    hdr->seq16 = 0;

    // 填写ICMP数据部分，包括IP数据报首部和IP数据报的前8个字节的数据字段，填写校验和
    memcpy(txbuf.data + sizeof(icmp_hdr_t), recv_buf->data, sizeof(ip_hdr_t) + 8);
    hdr->checksum16 = 0;
    hdr->checksum16 = checksum16((uint16_t*)hdr, txbuf.len);

    // 发送数据报
    ip_out(&txbuf, src_ip, NET_PROTOCOL_ICMP);
}

/**
 * @brief 初始化icmp协议
 * 
 */
void icmp_init(){
    net_add_protocol(NET_PROTOCOL_ICMP, icmp_in);
}